<?php

abstract class Color {

    const BYTE_SIZE = 256;

    /**
     * @return RgbColor
     */
    public static function createFromDec($dec) {

        $redByte   = round(($dec & 0xff0000) / 0xffff);
        $greenByte = round(($dec & 0x00ff00) / 0xff);
        $blueByte  = round(($dec & 0x0000ff));

        return self::createFromRGBBytes($redByte, $greenByte, $blueByte);
    }

    /**
     * @return RgbColor
     */
    public static function createFromRGBBytes($redByte, $greenByte, $blueByte) {

        if ($redByte < 0 || $redByte >= self::BYTE_SIZE)
            throw new Exception('red byte size incorrect - ' . $redByte);

        if ($greenByte < 0 || $greenByte >= self::BYTE_SIZE)
            throw new Exception('green byte size incorrect - ' . $greenByte);

        if ($blueByte < 0 || $blueByte >= self::BYTE_SIZE)
            throw new Exception('blue byte size incorrect - ' . $blueByte);

        return new RgbColor(
            $redByte   / (self::BYTE_SIZE - 1),
            $greenByte / (self::BYTE_SIZE - 1),
            $blueByte  / (self::BYTE_SIZE - 1)
        );
    }

    /**
     * @return RgbColor
     */
    public static function createFromRGBPercents($redPercent, $greenPercent, $bluePercent) {

        if ($redPercent < 0 || $redPercent > 100)
            throw new Exception('red percent incorrect - ' . $redPercent);

        if ($greenPercent < 0 || $greenPercent > 100)
            throw new Exception('green percent incorrect - ' . $greenPercent);

        if ($bluePercent < 0 || $bluePercent > 100)
            throw new Exception('blue percent incorrect - ' . $bluePercent);

        return new RgbColor(
            $redPercent / 100,
            $greenPercent / 100,
            $bluePercent / 100
        );
    }

    public function getHtmlCode() {
        return '#' . $this->getHex();
    }

    public function getDec() {
        return round((self::BYTE_SIZE - 1) * $this->getRed())   * self::BYTE_SIZE * self::BYTE_SIZE +
               round((self::BYTE_SIZE - 1) * $this->getGreen()) * self::BYTE_SIZE +
               round((self::BYTE_SIZE - 1) * $this->getBlue());
    }

    public function getHex() {
        return str_pad(dechex($this->getDec()), 6, '0', STR_PAD_LEFT);
    }

    /**
     * @return RgbColor
     */
    abstract public function toRgb();

    abstract public function getRed();
    abstract public function getGreen();
    abstract public function getBlue();

    abstract public function getHue();
    abstract public function getSaturation();
    abstract public function getValue();

    /**
     * @return HsvColor
     */
    abstract public function toHsv();
}

class RgbColor extends Color{

    private $floatRed;
    private $floatGreen;
    private $floatBlue;

    public function __construct($floatRed, $floatGreen, $floatBlue) {
        $this->floatRed   = $floatRed;
        $this->floatGreen = $floatGreen;
        $this->floatBlue  = $floatBlue;
    }

    /**
     * @return RgbColor
     */
    public function toRgb() { return $this; }

    public function getRed() { return $this->floatRed; }
    public function getGreen() { return $this->floatGreen; }
    public function getBlue() { return $this->floatBlue; }

    /**
     * @return HsvColor
     */
    public function toHsv() {
        list($r, $g, $b) = self::rgb2hsv($this->floatRed, $this->floatGreen,  $this->blueByte);
        return new RgbColor($h, $s, $v);
    }

    public function getHue()        { return $this->toHsv()->getHue(); }
    public function getSaturation() { return $this->toHsv()->getSaturation(); }
    public function getValue()      { return $this->toHsv()->getValue(); }

    private static function rgb2hsv($r, $g, $b) {

        $v = max($r, $g, $b);
        $minRGB = min($r, $g, $b);

        $s = ($v == 0) ? 0 : ($v - $minRGB) / $v;

        if ($s == 0) {
            $h = null;
        } else {

            $cr = ($v - $r) / ($v - $minRGB);
            $cg = ($v - $g) / ($v - $minRGB);
            $cb = ($v - $b) / ($v - $minRGB);

            if ($r == $v) $h = $cb - $cg;
            if ($g == $v) $h = 2 + $cr - $cb;
            if ($b == $v) $h = 4 + $cg - $cr;

            $h /= 6;
            if ($h < 0) $h++;
        }

        return array($h, $s, $v);
    }
}

class HsvColor extends Color{

    private $floatHue;
    private $floatSaturation;
    private $floatValue;

    public function __construct($floatHue, $floatSaturation, $floatValue) {

        if ($floatHue < 0 || $floatHue > 1)
            throw new Exception('hue must be between 0 and 1');

        if ($floatSaturation < 0 || $floatSaturation > 1)
            throw new Exception('saturation must be between 0 and 1');

        if ($floatValue < 0 || $floatValue > 1)
            throw new Exception('value must be between 0 and 1');

        $this->floatHue  = $floatHue;
        $this->floatSaturation = $floatSaturation;
        $this->floatValue = $floatValue;
    }

    /**
     * @return HsvColor
     */
    public function toHsv() { return $this; }

    public function getHue()        { return $this->floatHue; }
    public function getSaturation() { return $this->floatSaturation; }
    public function getValue()      { return $this->floatValue; }

    /**
     * @return RgbColor
     */
    public function toRgb() {
        list($r, $g, $b) = self::hsv2rgb($this->floatHue, $this->floatSaturation, $this->floatValue);
        return new RgbColor($r, $g, $b);
    }

    public function getRed()   { return $this->toRgb()->getRed(); }
    public function getGreen() { return $this->toRgb()->getGreen(); }
    public function getBlue()  { return $this->toRgb()->getBlue(); }

    private static function hsv2rgb($h, $s, $v) {

        $h = ($h - floor($h)) * 360;
        $r = $g = $b = 0;

        if ($s == 0) {
            $r = $g = $b = $v;
        } else {

            $h = ($h == 360) ? 0 : ($h / 60);
            $i = floor($h);
            $f = $h - $i;
            $m = $v * (1 - $s);
            $n = $v * (1 - $s * $f);
            $k = $v * (1 - $s * (1 - $f));

            switch ($i) {
                case 0: $r = $v; $g = $k; $b = $m; break;
                case 1: $r = $n; $g = $v; $b = $m; break;
                case 2: $r = $m; $g = $v; $b = $k; break;
                case 3: $r = $m; $g = $n; $b = $v; break;
                case 4: $r = $k; $g = $m; $b = $v; break;
                case 5: $r = $v; $g = $m; $b = $n; break;
            }
        }

        return array($r, $g, $b);
    }
}


?>